/**
 * @author mark.forster@hedgehoglab.com
 */

/**
 * 
 * @param {Object} scope
 */
function BaseObject(scope){
	EventDelegator.call(this,scope);
}

/**
 * 
 * @param {Object} scope
 */
function BaseModel(scope){
	BaseObject.call(this,scope);
	var _this=this;
	var _model=null;
	
	/**
	 * Set/Get the _model instance
	 * @param {Object} model
	 */
	this.model=function(model){
		if(arguments.length>0){
			_model=model;
			_this.raise_event('model_changed',{model:_model});
		}
	}
}

/**
 * Store JSON objects in a cookie style (Not sure of memory limits)
 * @param {Object} scope
 */
function JsonService(scope){
	var _this=this;
	
	/**
	 * Pull data from a store by path name
	 * @param {Object} path
	 */
	this.pull_from_store=function(path){
		var _store=air.EncryptedLocalStore.getItem(path);
		var store={};
		if(_store!=null) {
			store=_store.readUTFBytes( _store.bytesAvailable )	
			if(store=="undefined"){
				store={};
			}else{
				store=(""+store).replace(/\: undefined/gi,': null');
				store=$.parseJSON(store);
			}
		}else{
			store={};
		}
		
		return store;
	};
	
	/**
	 * Push data onto the store by path name
	 * @param {Object} path
	 * @param {Object} obj
	 */
	this.push_to_store=function(path,obj){
		data = new air.ByteArray();
		data.writeUTFBytes( $.stringify(obj) );
		air.EncryptedLocalStore.setItem(path,data);
	};
}

/**
 * 
 * @param {Object} scope
 */
function ApplicationController(scope){
	BaseObject.call(this,scope);
	var _id=null;
	
		
	//	Hook onto the timer service for change events
	scope.timer_service.subscribe_to('on_time_changed',function(e){
		scope.dashboard_view.show_time(e.time);
	});
	
	//	Hook onto the dashboard for start logging events
	scope.dashboard_view.subscribe_to('on_start_logging_time',function(e){
		if(!scope.timer_service.is_timing()){
			scope.timer_service.start_timing();
		}
	});
	
	//	Hook onto the dashboard for stop logging events
	scope.dashboard_view.subscribe_to('on_stop_logging_time',function(e){
		scope.timer_service.stop_timing();
	});
	
	//	Hook onto the dashboard for cancel logging events
	scope.dashboard_view.subscribe_to('on_cancel_log_description',function(e){
		scope.timer_service.start_timing();
	});
	
	//	Hook onto the dashboard for log description events
	scope.dashboard_view.subscribe_to('on_log_description',function(e){
		var secs=Math.ceil(scope.timer_service.get_time().seconds/60.0);
		var hours=scope.timer_service.get_time().hours*1.0;
		var mins=(((scope.timer_service.get_time().minutes+secs)*1.0)/60.0);
		
		var effort=parseInt((hours+mins)*1000.0)/1000;
		var description=(e.description!="")?e.description:"No description provided.";
		
		scope.issue_service.log_time_against_issue({
			id:_id,
			effort:effort,
			description:description
		},function(){
			scope.timer_service.stop_timing();
			scope.timer_service.reset_time();
		});
	});
	
	//	Hook onto the dashboard for log out events
	scope.dashboard_view.subscribe_to('on_log_out',function(e){
		logout();
	});
	
	//	Hook onto the dashboard for issue id change events
	scope.dashboard_view.subscribe_to('on_entering_issue_id',function(e){
		if (!scope.timer_service.is_timing()) {
			scope.timer_service.reset_time();
			scope.timer_service.stop_timing();
		}
		
		//	Get the issue id an save it for later
		_id=e.id;
	});
	
	
	//	Hook onto the dashboard for login clicked events
	scope.login_view.subscribe_to('on_login_clicked',function(e){
		if(e.uri=="undefened") e.uri==null;
		if(e.username=="undefened") e.username==null;
		if(e.password=="undefened") e.password==null;
		
		scope.json_service.push_to_store('creds',e);
		
		scope.user_service.authenticate(e,function(f){
			if (f != null) {
				dashboard();
			}else{
				alert("Your credentials could not be authenticated");
			}
		});
	});
	
	//	Hook onto the dashboard for exit clicked events
	scope.login_view.subscribe_to('on_exit_clicked',function(e){
		air.NativeApplication.nativeApplication.exit();	
	});
	
	/**
	 * Show and configure for Dashboard
	 */
	function dashboard(){
		scope.login_view.hide();
		scope.dashboard_view.show();
	}

	/**
	 * Show and configure for Login
	 */
	function login(){
		scope.timer_service.reset_time();
		scope.login_view.show();
	}
	
	//	Logout
	function logout(){
		scope.dashboard_view.hide();
		login();
	}
	
	//	Nothing left to do but show Login
	login();
}

/**
 * 
 * @param {Object} scope
 */
function BaseService(scope){
	BaseObject.call(this,scope);
	
	/**
	 * Base64 encode the user/password for basic authentication
	 * @param {Object} user
	 * @param {Object} password
	 */
	function make_base_auth(user, password) {
	  var tok = user + ':' + password;
	  var hash = Base64.encode(tok);
	  return "Basic " + hash;
	}
	
	/**
	 * A Base class method that wraps jqueries ajax call - all we need for getting/setting JSON
	 * @param {Object} options
	 */
	this.request=function(options){
		t=scope.json_service.pull_from_store('creds');
		
		$.ajax( { 
                url: options.uri,
                data: $.stringify(options.data),
                type: options.type,
                processData: false,
                timeout: 10000,
                dataType: "json",  // not "json" we'll parse
                username:t.username,
				password:t.password,
				
				beforeSend:function (ee){
					var auth= make_base_auth(t.username,t.password);
					ee.setRequestHeader('Authorization', auth);
					ee.setRequestHeader('Accept','application/json');
				},
                success:function(res){    
					options.callback(res);
                },
                error:function(e) {
					options.callback(null);
                    return;
                }
            });  
	}
}

/**
 * 
 * @param {Object} scope
 */
function UserService(scope){
	BaseService.call(this,scope);
	var _this=this;
	var _users=null;
	
	this.authenticate=function(credentials,callback){
		creds=scope.json_service.pull_from_store('creds');
		$.extend(creds,credentials);
		
		function on_users_loaded(data){
			callback(data);
		}

		_this.request({
			uri:creds.uri+'/api/users',
			data:null,
			type:'get',
			callback:on_users_loaded
		});
	};
}

/**
 * @todo this needs refactoring - its a bit stinky
 * @param {Object} scope
 */
function TimerService(scope){
	BaseObject.call(this,scope);
	
	var _time=0;
	var _this=this;
	var _timer_interval=null;
	var _snap_time=null;
	
	/**
	 * Execute every interval
	 * @param {Object} event
	 */
	function on_interval(event){
		var new_time=(new Date()).getTime();
		var d=new Date(new_time-_snap_time);
		_this.raise_event('on_time_changed',{
			time: {
				seconds:d.getSeconds(),
				minutes:d.getMinutes(),
				hours:d.getHours(),
				date:d
			}
		});
	}
	
	/**
	 * 
	 */
	this.start_timing=function(){
		if(!_snap_time) _snap_time = (new Date()).getTime();
		_timer_interval = setInterval(on_interval, 1);
	};
	
	/**
	 * 
	 */
	this.stop_timing=function(){
		clearInterval(_timer_interval);
		_timer_interval=null;
	};
	
	/**
	 * 
	 */
	this.reset_time=function(){
		_snap_time=null;
		_this.raise_event('on_time_changed',{
			time: {
				seconds: 0,
				minutes: 0,
				hours: 0,
				date: null
			}
		});
	};
	
	/**
	 * 
	 */
	this.is_timing=function(){
		return _timer_interval!=null;
	}
	
	/**
	 * 
	 */
	this.get_time=function(){
		var new_time=(new Date()).getTime();
		var d=new Date(new_time-_snap_time);
		return {
				seconds:d.getSeconds(),
				minutes:d.getMinutes(),
				hours:d.getHours(),
				date:d
			}
	}
}

/**
 * 
 * @param {Object} scope
 */
function IssueService(scope){
	BaseService.call(this,scope);
	var _this=this;
	
	/**
	 * Log time against an issue
	 * @param {Object} options {"id","effort","description"}
	 * @param {Object} callback
	 */
	this.log_time_against_issue=function(options,callback){
		function on_loaded(data){
			callback(data);
		}

		var creds=scope.json_service.pull_from_store('creds');

		_this.request({
			uri:creds.uri+'/api/issues/'+options.id+'/timelogs',
			data:{effort:options.effort,description:options.description},
			type:'post',
			callback:on_loaded
		});
	}
}

/**
 * Wrapper for html loader instance
 * @param {Object} scope
 */
function BaseView(scope){
	BaseObject.call(this,scope);
	var moveFunction = function() {nativeWindow.startMove()};
	
	//	Delegate the move event of the chrome to the title bar 
	$('div.title-bar',scope.window.root()).bind('mousemove',moveFunction);

	this.show=function(e){scope.window.show();};
	
	this.hide=function(e){scope.window.hide();};
	
	$('.hidden',scope.window.root()).hide().removeClass('hidden');
}

/**
 * @todo - implement a user model to handle authentication between services.
 */
function UserModel(){
	var _is_logged_in=false;
	
	this.login=function(){
		_is_logged_in=true;
	};
	
	this.logout=function(){
		_is_logged_in=false;
	};
}

/**
 * Abstraction of black book safe html loaders into a window/view manager
 * @param {Object} scope
 */
function WindowManager(scope){
	var loaders={};
	
	//WindowManager.loaders=loaders;
	
	this.get_window_for=function(view_name){
		return {
			show:function(){
				loaders[view_name].visible=true;
			},
			hide:function(){
				loaders[view_name].visible=false;
			},
			root:function(){
				return loaders[view_name].window.document;
			}
		}
	}
	
	/**
	 * Script totally ripped from black book to create html loaders
	 * @param {Object} path
	 * @param {Object} callback
	 */
	function createHTMLLoader (path, callback) {
		var loader = new air.HTMLLoader();
		loader.x = 0;
		loader.y = 0;
		loader.paintsDefaultBackground = false;
		loader.addEventListener(air.Event.COMPLETE, callback);
		loader.load(new air.URLRequest(path));
		return loader;
	}
	
	//	Load views
	
	var stage = htmlLoader.stage;
	stage.transform.perspectiveProjection.focalLength = 800;
	var paths = scope.paths;
	var leftToLoad = paths.length;
	
	var onLoadComplete = function(event) 
		{
			var loader = event.target;

			loader.width = loader.window.document.width;
			loader.height = loader.window.document.height;
		
			leftToLoad --;
			if (!leftToLoad) {
				scope.callback();
			};
		};

	for (var i = 0; i < paths.length; i++) {
		var loader = createHTMLLoader("/view/" + paths[i] + ".html", onLoadComplete);
		loader.x = 0;
		loader.y = 0;
		htmlLoader.stage.addChild(loader);
		loader.visible = false;
		loaders[paths[i]] = loader;
	}
}

/**
 * 
 * @param {Object} scope
 */
function LoginView(scope){
	BaseView.call(this,scope);	//Extend from BaseView
	var _this=this;

	$("input#login_submit_action",scope.window.root()).bind('click',function(){
		_this.raise_event('on_login_clicked',{
			'username':$("input#user_name",scope.window.root()).val()
			,'password':$("input#password",scope.window.root()).val()
			,'uri':$("input#uri",scope.window.root()).val()
		});
	});
	
	$("a#login_exit_action",scope.window.root()).bind('click',function(){
		_this.raise_event('on_exit_clicked',{});
	});
	
	var cred=$.extend({
		username:"",
		password:"",
		uri:""
	},scope.json_service.pull_from_store('creds'));
	
	$("input#user_name",scope.window.root()).val(cred.username);
	$("input#password",scope.window.root()).val(cred.password);
	$("input#uri",scope.window.root()).val(cred.uri);

}

/**
 * 
 * @param {Object} scope
 */
function DashboardView(scope){
	BaseView.call(this,scope);	//Extend from BaseView
	var _is_typing=false;
	var _toggle=false;
	
	var _this=this;
	
	//	Hide the description box and action before the view is visible
	$("#description_box",scope.window.root()).removeClass('hidden').hide();
	$("input#dashboard_log_description_action",scope.window.root()).removeClass('hidden').hide();
	
	$("a#dashboard_log_time_action",scope.window.root()).bind('click',function(){
		$(this).hide();
		$("#description_box",scope.window.root()).slideUp(250);
		$("a#dashboard_stop_log_time_action",scope.window.root()).show();
		$("input#issue_id",scope.window.root()).attr('disabled',true);
		_this.raise_event('on_start_logging_time',{});
	});
	
	//	Hook onto the stop button
	$("a#dashboard_stop_log_time_action",scope.window.root()).bind('click',function(){
		$(this).hide();

		//	Slide down the description box
		$("#description_box",scope.window.root()).slideDown(250,function(){
			$("input#dashboard_log_description_action",scope.window.root()).show();
			$("input#dashboard_cancel_log_description_action",scope.window.root()).show();
			$("a#dashboard_log_time_action",scope.window.root()).hide();
			
			$("input#issue_id",scope.window.root()).attr('disabled',false);
		});
		
		_this.raise_event('on_stop_logging_time',{});	
	});
	
	//
	$("input#dashboard_log_description_action",scope.window.root()).bind('click',function(){
		_this.raise_event('on_log_description',{
			description:$("textarea#description",scope.window.root()).val()
		});
		
		$("textarea#description",scope.window.root()).val("");
		$("input#dashboard_log_description_action",scope.window.root()).hide();
		$("input#dashboard_cancel_log_description_action",scope.window.root()).hide();
		
		$("input#issue_id",scope.window.root()).attr('disabled',false);
		
		$("a#dashboard_log_time_action",scope.window.root()).show();
		$('#description_box',scope.window.root()).slideUp(250);
	});
	
	//
	$("input#dashboard_cancel_log_description_action",scope.window.root()).bind('click',function(){
		$("input#dashboard_log_description_action",scope.window.root()).hide();
		$("input#dashboard_cancel_log_description_action",scope.window.root()).hide();
		
		$("a#dashboard_log_time_action",scope.window.root()).hide();
		$("a#dashboard_stop_log_time_action",scope.window.root()).show();
		$("#description_box",scope.window.root()).slideUp(250);
		
		_this.raise_event('on_cancel_log_description',{});
	});
	
	//
	$("a#dashboard_logout_action",scope.window.root()).bind('click',function(){		
		_this.raise_event('on_log_out');
	});
	
	//
	$("input#issue_id",scope.window.root()).bind('keyup',function(){
		_this.raise_event('on_entering_issue_id',{id:$(this).val()});
	});
	
	
	/**
	 * Public method to set the display time of this view
	 * @param {Object} time
	 */
	this.show_time=function(time){
		var hrs=(((""+time.hours).length==1)?"0":"")+time.hours;
		var mins=(((""+time.minutes).length==1)?"0":"")+time.minutes;
		var secs=(((""+time.seconds).length==1)?"0":"")+time.seconds
		
		$("#time",scope.window.root()).html(hrs+":"+mins+":"+secs);
	};
}

/**
 * jQuery entry point to documentRready
 */
$(function(){
	//	We only want to play with JSON in this application
	$.ajaxSetup({contentType:"application/json"});
	
	//	Create a proxy for airs EncryptedLocalStore
	var _json_service=new JsonService({});
	
	//	Create a windows manager to load in the views we need
	var _window_manager=new WindowManager({
		paths:["login","dashboard"],
		callback:on_windows_loaded
	});
	
	/**
	 * Wire up the layers when the windows have loaded
	 * @param {Object} event
	 */
	function on_windows_loaded(event){
		//	Create A model to store the time in to make it a bit more decoupled
		var _time_model=new BaseModel();
		
		//	User Service - Used to fetch user information and verify authentication	
		var _user_service=new UserService({json_service:_json_service});
		
		//	Provides an interface to retrieve,manipulate and store issues
		var _issue_service=new IssueService({json_service:_json_service});
		
		//	Provides an interface to a timer
		var _timer_service=new TimerService();
		
		//	Instance of a DashboardView, a wrapper for the dashboard htmlLoader
		var _dashboard_view=new DashboardView({
			window:_window_manager.get_window_for('dashboard')
			,time_model:_time_model
		});
		
		//	Instance of a LoginView, a wrapper for the dashboard htmlLoader
		var _login_view=new LoginView({
			window:_window_manager.get_window_for('login')
			,json_service:_json_service
		});
		
		//	Instance of the application controllers
		//	@todo - could do with some refactoring
		var _application_controller=new ApplicationController({
			login_view:_login_view
			,dashboard_view:_dashboard_view
			,user_service:_user_service
			,issue_service:_issue_service
			,timer_service:_timer_service,
			json_service:_json_service
		});
	}
});

/**
 * Event based abstration for message passing between instances
 * @param {Object} options
 */
function EventDelegator(options){
	if(this.__EVENT_DELEGATOR__) return;
	this.__EVENT_DELEGATOR__=true;
	var _subscriptions={};
	
	/**
	 * Provides an interface to subscribe to events
	 * @param {Object} event_key
	 * @param {Object} event_delegate
	 */
	this.subscribe_to=function(event_key,event_delegate){
		if(!_subscriptions[event_key]){
			_subscriptions[event_key]=[];
		}
		
		_subscriptions[event_key].push(event_delegate);
	};
	
	/**
	 * Provides an interface to un-subscribe to events
	 * @param {Object} event_key
	 * @param {Object} event_delegate
	 */
	this.unsubscribe_to=function(event_key,event_delegate){
		$.each(_subscriptions[event_key],function(i,n){
			if(event_delegate==n){
				_subscriptions[event_key][i]=null;
			}
		});
	}
	
	/**
	 * Raise an event so that subscribers of this event can be informed
	 * @param {Object} event_name
	 * @param {Object} args
	 */
	this.raise_event=function(event_name,args){
		function each_subscription(index,item){
			if(item){
				item(args);
			}
			
		}

		if (_subscriptions[event_name]) {
			$.each(_subscriptions[event_name], each_subscription);
		}
	};
}

//	Fin